/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.log;


import cn.easyplatform.lang.Nums;
import cn.easyplatform.messages.vos.EnvVo;
import cn.easyplatform.web.contexts.Contexts;
import cn.easyplatform.web.servlet.ApiGatewayServlet;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.filter.ThreadContextMapFilter;
import org.apache.logging.log4j.core.util.KeyValuePair;

import java.util.concurrent.TimeUnit;

/**
 * 前端日志监控工具类
 *
 * @Author: <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @Description:
 * @Since: 2.0.0 <br/>
 * @Date: Created in 2019/11/5 16:23
 * @Modified By:
 */
public final class LogManager {

    private final static String LOGGER_NAME = "cn.easyplatform.web";

    private final static String CONSOLE_APPENDER = "CONSOLE";

    private final static String API_LOGGER_NAME = ApiGatewayServlet.class.getName();

    private final static String REQUEST_COUNT = "request-count";

    private static final LoggerContext ctx = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);

    private static final Configuration config = ctx.getConfiguration();

    /**
     * 每个请求开始初始化MDC
     */
    public final static void beginRequest() {
        EnvVo env = Contexts.getEnv();
        if (env != null) {
            String s = ThreadContext.get(REQUEST_COUNT);
            int count = 0;
            if (s == null)
                ThreadContext.put(CONSOLE_APPENDER, (String) env.getSessionId());
            else count = Integer.parseInt(s);
            ThreadContext.put(REQUEST_COUNT, String.valueOf(++count));
        }
    }

    /**
     * 每个请求结束后移除MDC
     */
    public final static void endRequest() {
        String s = ThreadContext.get(REQUEST_COUNT);
        if (s != null) {
            int count = Nums.toInt(s, 1);
            count--;
            if (count == 0) {
                ThreadContext.remove(CONSOLE_APPENDER);
                ThreadContext.remove(REQUEST_COUNT);
            } else
                ThreadContext.put(REQUEST_COUNT, String.valueOf(count));
        }
    }

    /**
     * 以${项目id}+${用户id}+"-Api"作为主id，以${项目id}作为过滤元素,只监控本项目日志
     * 客户请求显示api动态日志
     */
    public final static void startApiConsole() {
        EnvVo env = Contexts.getEnv();
        String id = env.getSessionId() + "-Api";
        Appender appender = config.getAppender(id);
        if (appender != null)
            return;

        final Filter filter = ThreadContextMapFilter.createFilter(new KeyValuePair[]{KeyValuePair.newBuilder().setKey(ApiGatewayServlet.API_APPENDER).setValue(env.getProjectId()).build()}, null, Filter.Result.ACCEPT, Filter.Result.DENY);
        appender = new ConsoleAppender(id, filter, null, false, null).setApi(true).setEnv(env.getProjectId(), Contexts.getUser().getId());
        appender.start();
        config.addAppender(appender);
        final LoggerConfig loggerConfig = config.getLoggerConfig(API_LOGGER_NAME);
        loggerConfig.addAppender(appender, loggerConfig.getLevel(), filter);
        ctx.updateLoggers(config);
    }

    /**
     * 用户端结束api日志
     */
    public final static void stopApiConsole() {
        EnvVo env = Contexts.getEnv();
        if (env != null)
            removeAppender(env.getSessionId() + "-Api", API_LOGGER_NAME);
    }

    /**
     * 以${sessionId}+"-Browser"作为主id,以${sesionId}作为过滤元素，只监控用户本身的日志
     * 客户请求显示动态日志
     */
    public final static void startConsole() {
        EnvVo env = Contexts.getEnv();
        String sessionId = (String) env.getSessionId();
        String id = sessionId + "-Browser";
        Appender appender = config.getAppender(id);
        if (appender != null)
            return;

        final Filter filter = ThreadContextMapFilter.createFilter(new KeyValuePair[]{KeyValuePair.newBuilder().setKey(CONSOLE_APPENDER).setValue(sessionId).build()}, null, Filter.Result.ACCEPT, Filter.Result.DENY);
        appender = new ConsoleAppender(id, filter, null, false, null).setEnv(env.getProjectId(), Contexts.getUser().getId());
        appender.start();
        config.addAppender(appender);
        final LoggerConfig loggerConfig = config.getLoggerConfig(LOGGER_NAME);
        loggerConfig.addAppender(appender, Level.DEBUG, filter);
        ctx.updateLoggers(config);
    }

    /**
     * 用户端结束日志
     */
    public final static void stopConsole() {
        EnvVo env = Contexts.getEnv();
        if (env != null)
            removeAppender(env.getSessionId() + "-Browser", LOGGER_NAME);
    }

    /**
     * 停止实时日志
     */
    public final static void stop() {
        EnvVo env = Contexts.getEnv();
        if (env != null) {
            removeAppender(env.getSessionId() + "-Browser", LOGGER_NAME);
            removeAppender(env.getSessionId() + "-Api", API_LOGGER_NAME);
        }
    }

    /**
     * 移除appender
     *
     * @param id
     */
    private static synchronized void removeAppender(String id, String loggerName) {
        Appender appender = config.getAppender(id);
        if (appender != null) {
            //延时30秒关闭
            if (appender instanceof RollingRandomAccessFileAppender)
                ((RollingRandomAccessFileAppender) appender).stop(30, TimeUnit.SECONDS);
            else
                appender.stop();
            config.getAppenders().remove(id);
            config.getLoggerConfig(loggerName).removeAppender(id);
        }
    }

}
